using System;
using System.Data.Linq;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using HooIzDat.Web.Helpers;
using HooIzDat.Web.Models;
using HooIzDat.Web.Models.ViewModels;
using System.Threading;
using System.Transactions;

namespace HooIzDat.Web.Controllers
{
    public class GamesController : Controller
    {
        public IGameRepository Games { get; private set; }
        public IPlayerRepository Players { get; private set; }
        public ICharacterRepository Characters { get; private set; }
        public Player CurrentPlayer { get; set; }
        public bool IsAjax { get; set; }

        public GamesController()
            : this(null, null, null)
        { }

        public GamesController(IGameRepository games, IPlayerRepository players, ICharacterRepository people)
        {
            Games = games ?? new GameRepository();
            Players = players ?? new PlayerRepository();
            Characters = people ?? new CharacterRepository();
        }

        protected override void Initialize(System.Web.Routing.RequestContext requestContext)
        {
            base.Initialize(requestContext);
            // Get the current player
            CurrentPlayer = Players.GetPlayer(User.Identity.Name);
            IsAjax = Request.IsAjaxRequest();
            if (IsAjax) Response.Cache.SetCacheability(HttpCacheability.NoCache);
        }

        // GET: /Games/
        public ActionResult Index()
        {
            return RedirectToAction("Lobby");
        }

        // GET: /Games/Lobby
        //      /Games/Lobby?page=2
        [Authorize]
        public ActionResult Lobby(int? page)
        {
            // Check if user has an existing game
            var games = (from g in Games.FindActiveGames()
                        where g.Player1ID == CurrentPlayer.PlayerID || g.Player2ID == CurrentPlayer.PlayerID
                        select g);
            var game = games.FirstOrDefault();

            if (game != null && !(game.IsPending && game.IsExpired))
            {
                return IsAjax ?
                    Json(new { reload = true }) :
                    (ActionResult)RedirectToAction("Play", new { id = game.GameID });
            }

            // Build model and return view
            var leaderboard = (from g in Games.FindFinishedGames()
                               group g by g.Winner into winners
                               orderby winners.Count() descending,
                                       winners.Sum(g => g.QuestionsWonIn ?? 0) ascending
                               select new LeaderboardEntry
                               {
                                   Player = winners.Key,
                                   GamesWon = winners.Count(),
                                   TotalQuestions = winners.Sum(g => g.QuestionsWonIn ?? 0)
                               })
                               .Take(8);

            const int pageSize = 2;
            var pageIndex = page.HasValue ? page.Value - 1 : 0;
            var pendingGames = new PaginatedList<Game>(Games.FindPendingGames(), pageIndex, pageSize);

            return IsAjax ?
                PartialView("_PendingGames", pendingGames) :
                (ActionResult)View("Lobby", new GamesLobbyModel
                {
                    PendingGames = pendingGames,
                    Leaderboard = leaderboard
                });
        }

        // POST: /Games/New
        [Authorize]
        [HttpPost]
        public ActionResult New()
        {
            var game = new Game
            {
                CreatedOn = DateTime.Now,
                Player1 = CurrentPlayer
            };
            Games.Add(game);
            Games.Save();
            return RedirectToAction("Play", new { id = game.GameID });
        }

        // POST: /Games/Play/1/Join
        [Authorize]
        [HttpPost]
        public ActionResult Join(int id)
        {
            var game = Games.FindPendingGames().SingleOrDefault(g => g.GameID == id);
            if (game == null || game.Player2 != null)
                return GameCannotBeJoined();

            game.Join(CurrentPlayer, Characters.FindAllCharacterIds());

            try { Games.Save(); }
            catch (ChangeConflictException) { return GameCannotBeJoined(); }

            return RedirectToAction("Play", new { id = id });
        }

        private ActionResult GameCannotBeJoined()
        {
            ViewBag.Message = "This game is no longer available to join. Please choose another game and try again.";
            return View("Lobby", new { id = "" });
        }

        // GET: /Games/Play/1
        [Authorize]
        public ActionResult Play(int id, string ajaxClientState)
        {
            var game = Games.GetGame(id);
            if (!GameIsValidForPlayer(game, CurrentPlayer))
            {
                ViewBag.Message = "Sorry, but that game doesn't exist or was deleted";
                return IsAjax ?
                    Json(new { reload = true }) :
                    Lobby(null);
            }

            // Check game is not expired
            if (game.IsPending && game.IsExpired)
            {
                ViewBag.Message = "Sorry, but the game timed out waiting for another player";
                return IsAjax ?
                    Json(new { reload = true }) :
                    Lobby(null);
            }

            // Build view model and return view
            var model = new GamesPlayModel(game, CurrentPlayer);
            if (game.GameState != null && game.IsWaitingForQuestion)
            {
                model.Questions = Characters.FindAllQuestions()
                                            .Select(q => new SelectListItem { Text = q });
            }

            return IsAjax ?
                Json(new { reload = IsAjaxReloadRequired(game, ajaxClientState) }) :
                (ActionResult)View(model);
        }

        // POST: /Games/Play/1/AskQuestion
        [Authorize]
        [HttpPost]
        public ActionResult AskQuestion(int id, string question)
        {
            var game = Games.GetGame(id);
            if (!GameIsValidForPlayer(game, CurrentPlayer))
                return View("NotFound");

            // Check game state
            if (!game.IsWaitingForQuestion)
                return RedirectToAction("Play", new { id = id });

            // Check question is valid
            if (!Characters.FindAllQuestions().Contains(question))
            {
                ViewBag.Message = "The question you asked was invalid.";
                ViewData["GameID"] = id;
                return View();
            }

            // Update game state
            game.GameState.QuestionAsked = question;
            try
            {
                Games.Save();
            }
            catch (ChangeConflictException)
            {
                // Fall through to redirect action
            }

            return RedirectToAction("Play", new { id = id });
        }

        // POST: /Games/Play/1/Guess
        [Authorize]
        [HttpPost]
        public ActionResult Guess(int id, int tileId)
        {
            var game = Games.GetGame(id);
            if (!GameIsValidForPlayer(game, CurrentPlayer))
                return View("NotFound");

            // Check game state
            if (!game.IsWaitingForQuestion)
                return RedirectToAction("Play", new { id = id });

            // Load guessed character
            var character = Characters.GetCharacter(tileId);
            if (character == null)
                return RedirectToAction("Play", new { id = id });

            // Make the guess
            var guessIsCorrect = game.Player1 == CurrentPlayer && game.Player2Character == character ||
                                 game.Player2 == CurrentPlayer && game.Player1Character == character;

            // Update game state
            if (!guessIsCorrect)
            {
                game.GameState.GuessedCharacter = character;
                game.GameState.QuestionAsked = "Is your person " + character.Name + "?";
                game.GameState.Answer = false;
                var guessedTile = game.Tiles.SingleOrDefault(t => t.Player == CurrentPlayer && t.Character == character);
                guessedTile.IsOpen = false;
            }
            else
            {
                game.Won();
            }

            try { Games.Save(); }
            catch (ChangeConflictException) { } // Fall through to redirect action

            return RedirectToAction("Play", new { id = id });
        }

        // POST: /Games/Play/1/AnswerQuestion
        [Authorize]
        [HttpPost]
        public ActionResult AnswerQuestion(int id, string answer)
        {
            // TODO: Use a custom model binder here for the answer (true|false)
            var game = Games.GetGame(id);
            if (!GameIsValidForPlayer(game, CurrentPlayer))
                return View("NotFound");

            // Check game state
            if (!game.IsWaitingForAnswer)
                return RedirectToAction("Play", new { id = id });

            // Update game state
            game.GameState.Answer = (answer.Equals("Yes", StringComparison.InvariantCultureIgnoreCase));

            try { Games.Save(); }
            catch (ChangeConflictException) { } // Fall through to redirect action

            return RedirectToAction("Play", new { id = id });
        }

        // POST: /Games/Play/1/ChangeTileState
        [Authorize]
        [HttpPost]
        public ActionResult ChangeTileState(int id, int tileId)
        {
            var game = Games.GetGame(id);
            if (!GameIsValidForPlayer(game, CurrentPlayer))
                return Lobby(null);

            // Check tile state
            var tile = game.Tiles.SingleOrDefault(t => t.Player == CurrentPlayer && t.CharacterID == tileId);
            if (tile == null || !tile.CanChangeState(CurrentPlayer))
                return Request.IsAjaxRequest() ?
                    new JsonResult() :
                    (ActionResult)RedirectToAction("Play", new { id = id });

            // Toggle tile status            
            tile.IsOpen = !tile.IsOpen;
            try { Games.Save(); }
            catch (ChangeConflictException) { } // Fall through to redirect action

            if (IsAjax)
            {
                var action = game.IsWaitingForQuestion ?
                    Url.Action("Guess") :
                    Url.Action("ChangeTileState");
                return Json(new { newAction = action, isOpen = tile.IsOpen });
            }
            else
            {
                return RedirectToAction("Play", new { id = id });
            }
        }

        // POST: /Games/Play/1/FinishTurn
        [Authorize]
        [HttpPost]
        public ActionResult FinishTurn(int id)
        {
            using (var transaction = new TransactionScope())
            {
                var game = Games.GetGame(id);
                if (!GameIsValidForPlayer(game, CurrentPlayer))
                    return View("NotFound");

                // Check game state
                if (!game.IsWaitingForNextTurn)
                    return RedirectToAction("Play", new { id = id });

                // Update game state
                game.FinishCurrentTurn();
                try { Games.Save(); }
                catch (ChangeConflictException) { } // Fall through to redirect action
                transaction.Complete();
            }
            return RedirectToAction("Play", new { id = id });
        }

        // POST: /Games/Play/1/FinishGame
        [Authorize]
        [HttpPost]
        public ActionResult FinishGame(int id)
        {
            var game = Games.GetGame(id);
            if (!GameIsValidForPlayer(game, CurrentPlayer))
                return View("NotFound");

            // Check game state
            if (!game.IsFinished)
                return RedirectToAction("Play", new { id = id });

            return RedirectToAction("Lobby", new { id = "" });
        }

        // POST: /Games/Play/1/Cancel
        [Authorize]
        [HttpPost]
        public ActionResult Cancel(int id)
        {
            var game = Games.GetGame(id);
            if (game != null && !GameIsValidForPlayer(game, CurrentPlayer))
            {
                ViewBag.Message = "This game was already cancelled!";
                return Lobby(null);
            }

            if (game != null && !game.IsFinished)
            {
                Games.Delete(game);
                try { Games.Save(); }
                catch (ChangeConflictException) { } // Fall through to redirect action
            }

            return RedirectToAction("Lobby", new { id = "" });
        }

        // GET: /Games/DeleteAllPending
        [Authorize]
        public ActionResult DeleteAllPending(bool? confirm)
        {
            if (confirm.HasValue && confirm.Value)
            {
                Games.Delete(Games.FindPendingGames());
                Games.Save();
                ViewBag.Message = "Pending games deleted";
            }
            else
            {
                ViewBag.Message = "Pending games were not deleted";
            }
            return Lobby(null);
        }

        /// <summary>
        /// Determines whether an ajax reload is required based on the client state.
        /// </summary>
        private static bool IsAjaxReloadRequired(Game game, string ajaxClientState)
        {
            var activePlayer = ajaxClientState.Split(',')[0];
            var waitingFor = ajaxClientState.Split(',')[1];
            return game.IsFinished || game.IsExpired ||
                   !game.IsPending && game.GameState.ActivePlayerID.ToString() != activePlayer ||
                   waitingFor == "IsPending" && game.IsPending ||
                   waitingFor == "IsWaitingForQuestion" && game.IsWaitingForQuestion ||
                   waitingFor == "IsWaitingForAnswer" && game.IsWaitingForAnswer ||
                   waitingFor == "IsWaitingForAnswer" && game.IsWaitingForNextTurn ||
                   waitingFor == "IsWaitingForNextTurn" && game.IsWaitingForNextTurn;
        }

        private static bool GameIsValidForPlayer(Game game, Player player)
        {
            if (game == null || !game.HasPlayer(player))
                return false;
            else
                return true;
        }
    }
}